<template>
  <el-config-provider :locale="CONS.locale">
    <div class="upload">
      <div ref="topRef" class="top">
        <el-upload :http-request="upload" drag :show-file-list="false" element-loading-text="分片中"
          v-loading="sliceStyle.fullLoading">
          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
          <div class="el-upload__text">拖拽上传<em>点击上传</em></div>
          <template #tip>
            <div v-show="buttonStyle.isShowBtn"
              style="display: flex;justify-content: space-between; align-items: center;margin:20px">
              <div class="el-upload__tip" style="margin: 10px 0;font-size: 14px; font-weight: bold"><span
                  style="color: ;">并发数: {{ progressStyle.pool.length }}</span> -
                <span style="color: ;">上传完成数: {{ progressStyle.finish
                }} / {{ progressStyle.all }}</span> -
                失败数量: <span style="color:red"> {{
                  progressStyle.failList.length
                }}</span>

              </div>
              <div><el-button style="margin:0px 0" @click="pause" :loading="buttonStyle.btnLoading" type="primary">{{
                buttonStyle.btnStatus
              }}</el-button>

              </div>
            </div>

            <el-progress :stroke-width="16" v-show="buttonStyle.isShowBtn" :status="progressStyle.status"
              :indeterminate="progressStyle.striped" :striped="progressStyle.striped"
              :striped-flow="progressStyle.striped" :percentage="progressStyle.percentage" :color="customColorMethod" />
          </template>
        </el-upload>

      </div>
      <div>
        <el-scrollbar style="margin: 20px;" :height="issueInfo.mainHeight + 'px'">

          <FileCom v-model:loading="sunRefStyle.sonBtnLoading" @update:loading="sonUploading" :chash="sunRefStyle.chash"
            @update:fresh="sonFresh" ref="fileRef" @update:upload="sonUpload" :db="db"
            v-for="e in issueInfo.initDownLoadInfo" :item="e" :key="e.hash">
          </FileCom>
        </el-scrollbar>
      </div>

    </div>
  </el-config-provider>
</template>

<script setup lang="ts">
import { ref, watch, getCurrentInstance, nextTick, onMounted, onUnmounted, reactive } from "vue";
import { sFetch } from "./utils/request";
import { UploadFilled } from "@element-plus/icons-vue";
import FileCom from "./components/file.vue"
import { ElConfigProvider } from 'element-plus'
import zhCn from "element-plus/es/locale/lang/zh-cn";
import { base64toBlob, blobToBase64 } from "./utils/base64andblob"
import { add, get, getByKey } from "./utils/indexdb"
import { cumulateHash } from "./utils/cumulateHash";
import type { UploadRequestOptions, IinitDownLoadInfo } from "./types/app"
const { proxy } = getCurrentInstance() as any
const RFileName = reactive<{
  fileName: string,
  suffixValue: string
  fileHash: string
}>({
  fileName: "",
  suffixValue: "",
  fileHash: ""
})
const db = ref<any>("")
const CONS = reactive<{
  sliceSize: number,
  maxUploadNumber: number,
  locale: any
}>({
  sliceSize: 1024 * 1024,
  maxUploadNumber: 6,
  locale: zhCn
})

const issueInfo = reactive<{
  mainHeight: number,
  initDownLoadInfo: IinitDownLoadInfo[]
}>({
  mainHeight: 0,
  initDownLoadInfo: []
})

const progressStyle = reactive<{
  pool: any[],
  striped: boolean,//上传条纹
  status: "" | "success" | "warning" | "exception",//上传进度状态
  all: number,//所有文件
  finish: number,//已完成文件
  failList: any[],// 失败列表
  percentage: number
}>({
  pool: [], //线程池
  striped: false,//上传条纹
  status: "",//上传进度状态
  all: 0,//所有文件
  finish: 0,//已完成文件
  failList: [],// 失败列表
  percentage: 0// 上传比例
})
const sliceStyle = reactive<{
  sliceMax: number,//最大分片量
  fullLoading: boolean//分片中加载样式
}>({
  sliceMax: 0,//最大分片量
  fullLoading: false//分片中加载样式
})

const buttonStyle = reactive<{
  controller: any,//中止上传
  isPause: boolean,//是否暂停
  btnStatus: string,//是否暂停
  isShowBtn: boolean,//是否显示上传进度
  btnLoading: boolean//组件上传状态
}>({
  controller: null,//中止上传
  isPause: true,//是否暂停
  btnStatus: "暂停",//是否暂停
  isShowBtn: false,//是否显示上传进度
  btnLoading: false//组件上传状态
})

const getnamesuffix = (name: string) => {
  if (!name) {
    return false
  } else {
    let suffix = (name as any).match(/\.[^.]+$/g)[0].replace(/^./gi, '')
    let filename = (name as any).match(/^.+(?=\.)/)[0]
    return {
      suffix, filename
    }
  }

}

const sunRefStyle = reactive<{
  chash: string,
  sonBtnLoading: boolean
}>({
  chash: "",
  sonBtnLoading: false
})
const sonUploading = (item: any) => {
  sunRefStyle.sonBtnLoading = item.flag
  sunRefStyle.chash = item.hash
}
onMounted(() => {
  window.addEventListener('resize', getMainHeight);
})
onUnmounted(() => {
  window.removeEventListener('resize', getMainHeight);
})
const getMainHeight = () => {
  nextTick(() => {
    issueInfo.mainHeight = window.innerHeight - proxy.$refs.topRef.offsetHeight - 116
  })
}
const sonFresh = () => {
  getFileList('son')
}

watch(() => [progressStyle.percentage, buttonStyle.isPause], (arr) => {
  if (arr[0] == 100 || arr[0] == 0) {
    buttonStyle.isShowBtn = false
  } else {
    buttonStyle.isShowBtn = true
    if (arr[1] == true) {
      buttonStyle.btnStatus = '暂停'
    } else {
      buttonStyle.btnStatus = '继续'
    }
  }
  getMainHeight()
}, {
  deep: true,
  immediate: true
})
const sonUpload = (params: IinitDownLoadInfo) => {
  if (progressStyle.percentage === 0 || progressStyle.percentage === 100 || buttonStyle.isPause === false) {
    reset()
    progressStyle.status = ""
    cx(params.hash, params.name)
  } else {
    return ElMessage.warning('请先暂停上传或等待文件上传完毕')
  }
}

const getFileList = async (pos?: string) => {
  try {
    const res = await sFetch(
      `${import.meta.env.VITE_URL}getFileInfo`,
      "get",
    );
    if (res.code === 200) {
      issueInfo.initDownLoadInfo = res.data.map((e: IinitDownLoadInfo) => {
        return {
          name: e.name,
          max: e.max,
          hash: e.hash,
          cindex: e.cindex,
          hz: e.hz,
          url: e.url
        }
      })
    }
  } catch (error) {
    console.log(pos);


  }

}

const init = () => {
  // 打开数据库
  var request = window.indexedDB.open('fileDb', 4);
  request.onerror = function () {
    alert("无法打开数据库");
  };
  request.onsuccess = function () {
    db.value = request.result;
  };
  request.onupgradeneeded = function (event: any) {
    db.value = event.target.result;
    console.log("数据库升级成功");
    var objectStore = "" as any
    if (!db.value.objectStoreNames.contains('fileBase64')) {
      objectStore = db.value.createObjectStore('fileBase64', {
        keyPath: 'hash'
      });
    }
    //     objectStore.createIndex('hash', 'hash', { unique: true }) 中的第一个参数'hash'表示要创建的索引的名称。
    // 第二个参数'hash'表示要被索引的属性名称，也就是存储在对象中的哪个字段需要被索引。
    // { unique: true } 表示这个索引的值必须是唯一的，也就是说如果有两个对象具有相同的'hash'属性值，那么将会出现错误。
    objectStore.createIndex('hash', 'hash', { unique: true });
  }

  getMainHeight()
  getFileList('init')
  console.log(import.meta.env.VITE_URL);

}
init()
const getLocalSplice = async (hash: string, index: number) => {
  let res = await getByKey(db.value, "fileBase64", hash) as {
    hash: string,
    data: any[]
  }
  let CopyLocalIndexDbData = JSON.parse(JSON.stringify(res.data))
  let subList = [] as any
  CopyLocalIndexDbData.forEach((e: any, i: number) => {
    if (i >= index) {
      let obj = {
        hash: e.hash,
        blob: base64toBlob(e.base64)
      }
      subList.push(obj)
    }
  });
  return subList
}
const reset = () => {
  progressStyle.pool = []
  progressStyle.finish = 0
  progressStyle.failList = []
  progressStyle.striped = false
  progressStyle.status = ""
  progressStyle.all = 0
  progressStyle.percentage = 0
  buttonStyle.isPause = true
}
const customColorMethod = (percentage: number) => {
  let arrobj = {
    10: "#FF6347",
    20: "#FF7F50",
    30: "#FFA500",
    40: "#FFD700",
    50: "#FFEC8B",
    60: "#ADFF2F",
    70: "#7CFC00",
    80: "#00FF7F",
    90: "#00BFFF",
    100: "#9370DB",
  }
  if (percentage < 10) {
    return arrobj[10]
  }
  if (percentage < 20) {
    return arrobj[20]
  }
  if (percentage < 30) {
    return arrobj[30]
  }
  if (percentage < 40) {
    return arrobj[40]
  }
  if (percentage < 50) {
    return arrobj[50]
  }
  if (percentage < 60) {
    return arrobj[60]
  }
  if (percentage < 70) {
    return arrobj[70]
  }
  if (percentage < 80) {
    return arrobj[80]
  }
  if (percentage < 90) {
    return arrobj[90]
  }
  if (percentage < 100) {
    return arrobj[100]
  }

}
const cx = async (hash: string, name: string) => {
  try {
    const res = await sFetch(
      `${import.meta.env.VITE_URL}reload`,
      "get",
      {
        hash: hash,
        name: name
      }
    );
    if (res.code === 200) {
      let localSplice = await getLocalSplice(res.data.hash, res.data.index)
      buttonStyle.btnLoading = false
      sunRefStyle.sonBtnLoading = false
      progressStyle.failList = []
      RFileName.fileHash = res.data.hash
      RFileName.fileName = res.data.name
      RFileName.suffixValue = res.data.hz
      progressStyle.all = Number(res.data.max)
      progressStyle.finish = Number(res.data.index)
      console.log(localSplice, 'localSplice');
      uploadFileChunks(localSplice)
    }
  } catch (error) {
    console.log('cx', error);

  }

}



//分片
const slice = async (file: File, sliceSize: number) => {
  if (!file) alert('无文件');
  const fileChunks = []
  const { suffix, filename } = getnamesuffix(file.name) as any
  RFileName.fileName = filename
  RFileName.suffixValue = suffix
  let fileLength = Math.ceil(file.size / sliceSize)
  progressStyle.all = fileLength  //文件从0开始  需要上传对应下标
  let cur = 0;
  for (let i = 0; i < fileLength; i++) {
    let res = await blobToBase64(file.slice(cur, cur + sliceSize))
    fileChunks.push({
      hash: i,
      blob: file.slice(cur, cur + sliceSize),
      base64: res
    })
    cur += sliceSize
  }
  return fileChunks
}

const pause = async () => {
  if (buttonStyle.isPause) {
    try {
      buttonStyle.controller.abort()
      getFileList('pause')

    } catch (error) {
      console.log(error, 'pauseError');
    }
  } else {
    buttonStyle.btnLoading = true
    progressStyle.status = ""
    cx(RFileName.fileHash, RFileName.fileName)
  }
  buttonStyle.isPause = !buttonStyle.isPause
}

//上传分片
const uploadFileChunks = async (list: { hash: number, blob: Blob }[]) => {
  sliceStyle.fullLoading = false
  progressStyle.striped = true
  if (list.length === 0) {
    progressStyle.striped = false
    // 所有分片上传完成，通知合并
    try {
      const res = await sFetch(
        `${import.meta.env.VITE_URL}merge`,
        "get",
        {
          name: `${RFileName.fileHash}--${RFileName.fileName}||${RFileName.suffixValue}`
        }
      );
      if (res.code === 200) {
        ElMessage.success(res.msg)
        getFileList('uploadFinish')

      } else {
        ElMessage.error(res.msg)
      }
    } catch (error) {
      ElMessage.error(JSON.stringify(error))
    }
    return
  }

  buttonStyle.controller = new AbortController();
  const signal = buttonStyle.controller.signal;
  for (let i = 0; i < list.length; i++) {
    const blobName = `${RFileName.fileHash}//${RFileName.fileName}--${list[i].hash}||${RFileName.suffixValue}`;
    console.log(list[i].hash, 'hash', RFileName.fileHash, RFileName.fileName);
    const blobFile = new File([list[i].blob], blobName);
    let formData = new FormData();
    formData.append('file', blobFile);
    formData.append('sliceMax', sliceStyle.sliceMax as any);
    try {
      const task = sFetch(
        import.meta.env.VITE_URL,
        "post",
        formData,
        true,
        signal
      );
      task.then(res => {
        if (res.code === 200) {
          // 从并发池中移除已经完成的请求
          let index = progressStyle.pool.findIndex((t: any) => t === task);
          progressStyle.pool.splice(index);
          progressStyle.finish++;
        }
      }).catch((err: any) => {
        if (err === 'request abort') {
          progressStyle.status = "warning"
        } else {
          progressStyle.status = "exception"
          progressStyle.failList.push(list[i]);
        }
      }).finally(() => {
        progressStyle.percentage = Number((parseFloat((progressStyle.finish / progressStyle.all).toFixed(2)) * 100).toFixed(2))
        if (progressStyle.finish === progressStyle.all) {
          progressStyle.status = 'success'
          if (progressStyle.failList.length !== 0) {
            ElMessage.error('重新上传错误分片')
          }
          uploadFileChunks(progressStyle.failList);
        }
      });
      progressStyle.pool.push(task);

      if (progressStyle.pool.length === CONS.maxUploadNumber) {
        // 等待 线程池 完成任务
        try {
          await Promise.race(progressStyle.pool);
        } catch (error) {
          console.log(error, "Promise.race");
          if (error) {
            progressStyle.pool = []
            break;
          }
          ElMessage.error(JSON.stringify(error))
        }

      }
    } catch (error) {
      console.log(error, 'error');

    }

  }
};

const upload = async (e: UploadRequestOptions) => {
  if (progressStyle.percentage === 0 || progressStyle.percentage === 100 || buttonStyle.isPause === false) {
    sliceStyle.fullLoading = true
    reset()
    let blobs = await slice(e.file, CONS.sliceSize);
    sliceStyle.sliceMax = blobs.length
    let res = await cumulateHash(blobs, CONS.sliceSize)
    RFileName.fileHash = res;
    try {
      let indexGetRes = await get(db.value, 'fileBase64', res)
      if (!indexGetRes) {
        add(db.value, 'fileBase64', {
          hash: res, data: blobs.map(e => {
            return {
              hash: e.hash,
              base64: e.base64,
              name: RFileName.fileName + '.' + RFileName.suffixValue,
              sliceMax: blobs.length
            }
          })
        })
      }
    } catch (error) {
      console.log(error);

    }

    uploadFileChunks(blobs)
  }
  else {
    return ElMessage.warning('请先暂停上传或等待文件上传完毕')
  }

  //4 文件夹上传
  //5 数据库redis

};
</script>

<style scoped lang="scss">
$phone: 768px;
$tablet: 992px;

.upload {
  width: 100%;
  height: auto;
  margin: 30px auto;
  padding: 0 100px;
  box-sizing: border-box;

  @media (max-width: $phone) {
    // 在屏幕宽度小于等于768像素时应用的样式
    padding: 0 10px;
  }

  @media (min-width: $tablet) {
    // 在屏幕宽度大于992像素时应用的样式
    padding: 0 400px;
  }
}
</style>
